package com.ruoyi.common.utils;

import com.github.junrar.Archive;
import com.github.junrar.Junrar;
import com.github.junrar.rarfile.FileHeader;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.AesKeyStrength;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.sf.sevenzipjbinding.*;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

public class ZipUtils {
    public static void zip(List<File> srcFiles,
                           String zipPath,String password) throws ZipException {
        zip(srcFiles, new File(zipPath), password);
    }

    public static void zip(List<File> srcFiles,
                           File zipPath,String password) throws ZipException {
        if(password==null)return;
        ZipParameters zipParameters = new ZipParameters();
        zipParameters.setEncryptFiles(true);
        zipParameters.setEncryptionMethod(EncryptionMethod.AES);
        // Below line is optional. AES 256 is used by default. You can override it to use AES 128. AES 192 is supported only for extracting.
        zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);

        ZipParameters zipFileParameters = new ZipParameters();
        zipFileParameters.setEncryptFiles(true);
        zipFileParameters.setEncryptionMethod(EncryptionMethod.AES);
        // Below line is optional. AES 256 is used by default. You can override it to use AES 128. AES 192 is supported only for extracting.
        zipFileParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
        zipFileParameters.setIncludeRootFolder(false);

        ZipFile zipFile = new ZipFile(zipPath, password.toCharArray());
        for(int i=0;i<srcFiles.size();i++){
            File subFile = srcFiles.get(i);
            if(subFile.isFile()){
                zipFile.addFile(subFile, zipFileParameters);
            }else if(subFile.isDirectory()){
                zipFile.addFolder(subFile, zipParameters);
            }
        }
    }

    public static void zip(List<File> srcFiles,
                           File zipPath) throws ZipException {
        ZipFile zipFile = new ZipFile(zipPath);
        for(int i=0;i<srcFiles.size();i++){
            File subFile = srcFiles.get(i);
            if(subFile.isFile()){
                zipFile.addFile(subFile);
            }else if(subFile.isDirectory()){
                zipFile.addFolder(subFile);
            }
        }
    }



    /**
     *
     * @param srcPath  源文件
     * @param zipPath
     * @param withSrcDir  如果源文件是文件夹是否把该作为第一层压缩
     * @param password  压缩密码
     * @throws IOException
     */
    public static void zip(String srcPath, String zipPath,boolean withSrcDir ,String password) throws IOException {
        if(password==null)return;
        ZipParameters zipParameters = new ZipParameters();
        zipParameters.setEncryptFiles(true);
        zipParameters.setEncryptionMethod(EncryptionMethod.AES);
        // Below line is optional. AES 256 is used by default. You can override it to use AES 128. AES 192 is supported only for extracting.
        zipParameters.setAesKeyStrength(AesKeyStrength.KEY_STRENGTH_256);
        ZipFile zipFile = new ZipFile(zipPath, password.toCharArray());
        File srcFile = new File(srcPath);
        if(withSrcDir){
            if(srcFile.isFile()){
                zipFile.addFile(srcFile, zipParameters);
            }else if(srcFile.isDirectory()){
                zipFile.addFolder(srcFile, zipParameters);
            }
        }if(srcFile.isFile()){
            zipFile.addFile(srcFile, zipParameters);
        }else if(srcFile.isDirectory()){
            File[] subFiles = srcFile.listFiles();
            for(int i=0;i<subFiles.length;i++){
                File subFile = subFiles[i];
                if(subFile.isFile()){
                    zipFile.addFile(subFile, zipParameters);
                }else if(subFile.isDirectory()){
                    zipFile.addFolder(subFile, zipParameters);
                }
            }
        }
    }

    public static void zip(String srcPath, String zipPath,boolean withSrcDir) throws IOException {
        ZipFile zipFile = new ZipFile(zipPath);
        File srcFile = new File(srcPath);
        if(withSrcDir){
            if(srcFile.isFile()){
                zipFile.addFile(srcFile);
            }else if(srcFile.isDirectory()){
                zipFile.addFolder(srcFile);
            }
        }if(srcFile.isFile()){
            zipFile.addFile(srcFile);
        }else if(srcFile.isDirectory()){
            File[] subFiles = srcFile.listFiles();
            for(int i=0;i<subFiles.length;i++){
                File subFile = subFiles[i];
                if(subFile.isFile()){
                    zipFile.addFile(subFile);
                }else if(subFile.isDirectory()){
                    zipFile.addFolder(subFile);
                }
            }
        }
    }
    public static void unzip(String zipFilePath, String outFileDir,String password) throws ZipException {
        ZipFile zipFile = new ZipFile(zipFilePath, password.toCharArray());
        zipFile.extractAll(outFileDir);
    }
    public static void unzip(String zipFilePath, String outFileDir) throws ZipException {
        ZipFile zipFile = new ZipFile(zipFilePath);
        zipFile.extractAll(outFileDir);
    }

    //待解压的文件名是否乱码
    private static boolean isRandomCode(List<net.lingala.zip4j.model.FileHeader> fileHeaders) {
        for (int i = 0; i < fileHeaders.size(); i++) {
            net.lingala.zip4j.model.FileHeader fileHeader = fileHeaders.get(i);
            boolean canEnCode = Charset.forName("GBK").newEncoder().canEncode(fileHeader.getFileName());
            if (!canEnCode) {//canEnCode为true，表示不是乱码。false.表示乱码。是乱码则需要重新设置编码格式
                return true;
            }
        }
        return false;
    }

    public static void unzip(File srcFile, String outFileDir) throws IOException {
        //https://blog.csdn.net/tongsiw/article/details/125511071
        ZipFile zFile = new ZipFile(srcFile);
        zFile.setCharset(StandardCharsets.UTF_8);
        List<net.lingala.zip4j.model.FileHeader> headers = zFile.getFileHeaders();
        if (isRandomCode(headers)) {//判断文件名是否有乱码，有乱码，将编码格式设置成GBK
            zFile.close();
            zFile = new net.lingala.zip4j.ZipFile(srcFile);
            zFile.setCharset(Charset.forName("GBK"));
        }
        if (!zFile.isValidZipFile()) {
            throw new ZipException("压缩文件不合法,可能被损坏.");
        }
//        if (zFile.isEncrypted() && !TextUtils.isEmpty(password)) {//加密zip，且输入的密码不为空，直接进行解密。
//            zFile.setPassword(password.toCharArray());
//        }
        File destDir = new File(outFileDir);
        if (!destDir.getParentFile().exists()) {
            destDir.mkdir();
        }
        zFile.extractAll(outFileDir);
    }


    //////////////////////////////////////////////////////
    //<dependency>
    //    <groupId>com.github.junrar</groupId>
    //    <artifactId>junrar</artifactId>
    //    <version>7.4.0</version>
    //</dependency>
    public static void unRar1(String rarFile, String outDir) throws Exception {
        Junrar.extract(rarFile, outDir);
    }

    /**
     * 解压Rar文件
     * https://blog.csdn.net/m0_61228138/article/details/122873973
     * @param rarFile 文件目录
     */
    public static File unRar2(File rarFile, String outDir) throws Exception {
        File outFileDir = new File(outDir);
        if (!outFileDir.exists()) {
            boolean isMakDir = outFileDir.mkdirs();
            if (isMakDir) {
                System.out.println("创建压缩目录成功");
            }
        }
        System.out.println(rarFile.getName());
        Archive archive = new Archive(new FileInputStream(rarFile));
        FileHeader fileHeader = archive.nextFileHeader();
        File out = null;
        while (fileHeader != null) {
            if (fileHeader.isDirectory()) {
                fileHeader = archive.nextFileHeader();
                continue;
            }
            out = new File(outDir + fileHeader.getFileNameString());
            if (!out.exists()) {
                if (!out.getParentFile().exists()) {
                    out.getParentFile().mkdirs();
                }
                out.createNewFile();
            }
            FileOutputStream os = new FileOutputStream(out);
            archive.extractFile(fileHeader, os);
            os.close();
            break;
        }
        archive.close();
        return out;
    }
    //https://www.jianshu.com/p/542363811285
    public static File unRar2(String rarFile, String outDir) throws Exception {
        return unRar2(new File(rarFile), outDir);
    }

    //https://sevenzipjbind.sourceforge.net/
    public static boolean unRar3(String sourceRarPath, String destDirPath, String passWord)
            throws IOException, SevenZipNativeInitializationException {
        String rarDir = sourceRarPath;
        String outDir = destDirPath;
        RandomAccessFile randomAccessFile = null;
        IInArchive inArchive = null;
        // 第一个参数是需要解压的压缩包路径，第二个参数参考JdkAPI文档的RandomAccessFile
        randomAccessFile = new RandomAccessFile(rarDir, "r");
//        System.getProperty("os.arch")
        System.setProperty("os.arch","amd64");
//        SevenZip.initSevenZipFromPlatformJAR("Linux-aarch64");//Linux-amd64
        SevenZip.initSevenZipFromPlatformJAR();
        //Error loading SevenZipJBinding native library into JVM: Can't find suited platform for os.arch=aarch64, os.name=Linux... Available list of platforms: Linux-amd64, Linux-i386, Mac-x86_64,
        // Windows-amd64, Windows-x86 [You may also try different
        // SevenZipJBinding initialization methods 'net.sf.sevenzipjbinding.SevenZip.init*()' in order to solve this problem]
        if (StringUtils.isNotBlank(passWord))
            inArchive = SevenZip.openInArchive(ArchiveFormat.RAR5, new RandomAccessFileInStream(randomAccessFile), passWord);
        else
            inArchive = SevenZip.openInArchive(ArchiveFormat.RAR5, new RandomAccessFileInStream(randomAccessFile));
        ISimpleInArchive simpleInArchive = inArchive.getSimpleInterface();
        for (final ISimpleInArchiveItem item : simpleInArchive.getArchiveItems()) {
            final int[] hash = new int[]{0};
            if (!item.isFolder()) {
                ExtractOperationResult result;
                final long[] sizeArray = new long[1];
                File outFile = new File(outDir + item.getPath());
                File parent = outFile.getParentFile();
                if ((!parent.exists()) && (!parent.mkdirs())) {
                    continue;
                }
                if (StringUtils.isNotBlank(passWord)) {
                    result = item.extractSlow(data -> {
                        try {
                            IOUtils.write(data, new FileOutputStream(outFile, true));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        hash[0] ^= Arrays.hashCode(data); // Consume data
                        sizeArray[0] += data.length;
                        return data.length; // Return amount of consumed
                    }, passWord);
                } else {
                    result = item.extractSlow(data -> {
                        try {
                            IOUtils.write(data, new FileOutputStream(outFile, true));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        hash[0] ^= Arrays.hashCode(data); // Consume data
                        sizeArray[0] += data.length;
                        return data.length; // Return amount of consumed
                    });
                }

                if (result == ExtractOperationResult.OK) {
//                        log.error("解压rar成功...." + String.format("%9X | %10s | %s", hash[0], sizeArray[0], item.getPath()));
                }
            }
        }
        inArchive.close();
        randomAccessFile.close();
        return true;
    }

    //////////////////////////////////////////////////////
    public static String un7z(String sourceRarPath, String destDirPath, String passWord) {
        try {
            File srcFile = new File(sourceRarPath);//获取当前压缩文件
            // 判断源文件是否存在
            if (!srcFile.exists()) {
                throw new Exception(srcFile.getPath() + "所指文件不存在");
            }
            //开始解压
            SevenZFile zIn = null;
            if (StringUtils.isNotBlank(passWord)) {
                zIn = new SevenZFile(srcFile, passWord.getBytes());
            } else {
                zIn = new SevenZFile(srcFile);
            }

            SevenZArchiveEntry entry = null;
            File file = null;
            while ((entry = zIn.getNextEntry()) != null) {
                if (!entry.isDirectory()) {
                    file = new File(destDirPath, entry.getName());
                    if (!file.exists()) {
                        new File(file.getParent()).mkdirs();//创建此文件的上级目录
                    }
                    OutputStream out = new FileOutputStream(file);
                    BufferedOutputStream bos = new BufferedOutputStream(out);
                    int len = -1;
                    byte[] buf = new byte[1024];
                    while ((len = zIn.read(buf)) != -1) {
                        bos.write(buf, 0, len);
                    }
                    // 关流顺序，先打开的后关闭
                    bos.close();
                    out.close();
                }
            }

        } catch (Exception e) {
//            log.error("un7z is error", e);
            return e.getMessage();
        }
        return "";
    }
}
